package com.ethink.gateway.filter.post;

import com.ethink.framework.common.util.PathMatcherUtil;
import com.ethink.gateway.constant.GatewayConstant;
import com.ethink.gateway.filter.AbstractPostFilter;
import com.ethink.gateway.model.RequestInfo;
import com.ethink.gateway.model.ResponseInfo;
import com.ethink.gateway.properties.GateWayCommonProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class ResponseLogFilter extends AbstractPostFilter {

    @Autowired
    private GateWayCommonProperties commonProperties;

    @Override
    public int getOrder() {
        return GatewayConstant.FILTER_ORDER_RESPONSE_LOG;
    }

    @Override
    public ServerWebExchange postFilter(ServerWebExchange exchange) {
        try {
            if (PathMatcherUtil.notMatch(commonProperties.getRequestLogExcludePaths(), exchange.getRequest().getPath().value())) {
                return exchange.mutate().response(new RecordServerHttpResponseDecorator(exchange, commonProperties.getResponseLogMaxLength())).build();
            }
        } catch (Exception e) {
            log.error("响应日志打印异常", e);
        }
        return exchange;
    }

    static class RecordServerHttpResponseDecorator extends ServerHttpResponseDecorator {

        private ServerWebExchange exchange;
        private DataBufferFactory bufferFactory;
        private Integer responseLogMaxLength;

        public RecordServerHttpResponseDecorator(ServerWebExchange exchange, Integer responseLogMaxLength) {
            super(exchange.getResponse());
            this.exchange = exchange;
            this.responseLogMaxLength = responseLogMaxLength;
            this.bufferFactory = exchange.getResponse().bufferFactory();
        }

        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
            if (body instanceof Flux) {
                Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                    DataBuffer join = bufferFactory.join(dataBuffers);
                    byte[] content = new byte[join.readableByteCount()];
                    join.read(content);
                    DataBufferUtils.release(join);
                    ResponseInfo responseInfo = buildResponseInfo(exchange, content);
                    log.info("response: {}", responseInfo);
                    return bufferFactory.wrap(content);
                }));
            }
            return super.writeWith(body);
        }

        private ResponseInfo buildResponseInfo(ServerWebExchange exchange, byte[] content) {
            RequestInfo requestInfo = exchange.getAttribute(GatewayConstant.ATTR_REQUEST_INFO);
            MediaType mediaType = exchange.getResponse().getHeaders().getContentType();
            long now = System.currentTimeMillis();
            ResponseInfo responseInfo = new ResponseInfo();
            responseInfo.setResponseTime(now);
            responseInfo.setElapsedTime(requestInfo == null ? 0 : now - requestInfo.getRequestTime());
            responseInfo.setTraceId(responseInfo.getTraceId());
            responseInfo.setRequestTime(responseInfo.getRequestTime());
            responseInfo.setContentLength(exchange.getResponse().getHeaders().getContentLength());
            if (MediaType.APPLICATION_JSON.equalsTypeAndSubtype(mediaType) && content != null) {
                String msg = new String(content);
                if (responseLogMaxLength != null && StringUtils.isNotBlank(msg) && msg.length() > responseLogMaxLength) {
                    msg = msg.substring(0, responseLogMaxLength) + "...";
                }
                responseInfo.setBodyStr(msg);
            }
            return responseInfo;
        }
    }
}
